#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


#include "els_vm.h"
#include "els_vm_bytecode.h"
#include "hash.h"
#include "inout.h"
#include "unit.h"
#include "els.h"
#include "catch.h"

#define STACK_SIZE 	128

#define tonumber(o) ((tag(o) != ELS_TYPE_NUMBER) && (els_tonumber(o) != 0))
#define tostring(o) ((tag(o) != ELS_TYPE_STRING) && (els_tostring(o) != 0))

#define EvmAddress(lo)     ((lo)+stack-1)
#define Ref(st)         ((st)-stack+1)
#define incr_top	if (++top >= stackLimit) growstack()
#define els_checkstack(nt)  if ((nt) >= stackLimit) growstack()
#define MAX_C_BLOCKS 10
static int numCblocks = 0;
static int Cblocks[MAX_C_BLOCKS];
static Object initial_stack;
static Object *stackLimit = &initial_stack+1;
static Object *stack = &initial_stack;
static Object *top = &initial_stack;

static int CBase = 0;
static int CnResults = 0; 
static  jmp_buf *errorJmp = NULL; /* current error recover point */
static int els_VmLoadByte (Byte *pc, int base);
static void do_call (int base, int nResults);


Object *els_vm_EvmAddress (els_Typesystem_Object o){
    return EvmAddress(o);
}

static void els_initstack (void){
    Long maxstack = STACK_SIZE;
    stack = newvector(maxstack, Object);
    stackLimit = stack+maxstack;
    top = stack;
    *(top++) = initial_stack;
}

static void growstack (void){
    if (stack == &initial_stack) els_initstack();
    else{
        int t = top-stack;
        Long maxstack = stackLimit - stack;
        maxstack *= 2;
        stack = growvector(stack, maxstack, Object);
        stackLimit = stack+maxstack;
        top = stack + t;
        if (maxstack >= MAX_WORD/2) els_error("锟斤拷栈锟斤拷锟�");
    }
}

static char *els_strconc (char *l, char *r){
    static char *buffer = NULL;
    static int buffer_size = 0;
    int nl = strlen(l);
    int n = nl+strlen(r)+1;
    if (n > buffer_size){
        buffer_size = n;
        if (buffer != NULL) els_vm_free(buffer);
        buffer = newvector(buffer_size, char);
    }
    strcpy(buffer,l);
    strcpy(buffer+nl, r);
    return buffer;
}

static int els_tonumber (Object *obj){
    double t;
    char c;
    if (tag(obj) != ELS_TYPE_STRING) return 1;
    else if (sscanf(svalue(obj), "%lf %c",&t, &c) == 1){
        nvalue(obj) = t;
        tag(obj) = ELS_TYPE_NUMBER;
        return 0;
    }else return 2;
}

static int els_tostring (Object *obj){
    static char s[256];
    if (tag(obj) != ELS_TYPE_NUMBER) return 1;
    if ((int) nvalue(obj) == nvalue(obj)) sprintf (s, "%d", (int) nvalue(obj));
    else sprintf (s, "%g", nvalue(obj));
    tsvalue(obj) = els_createstring(s);
    if (tsvalue(obj) == NULL) return 1;
    tag(obj) = ELS_TYPE_STRING;
    return 0;
}

static void compare_top (int newtop){
    Object *nt;
    els_checkstack(stack+newtop);
    nt = stack+newtop; 
    while (top < nt) tag(top++) = ELS_TYPE_NULL;
    top = nt;  
}

static void open_stack (int nelems){
    int i;
    for (i=0; i<nelems; i++) *(top-i) = *(top-i-1);
    incr_top;
}

static int callC (els_Typesystem_CERRORPI func, int base){
    int oldBase = CBase;
    int oldCnResults = CnResults;
    int firstResult;
    CnResults = (top-stack) - base;
    CBase = base+CnResults;
    (*func)();
    firstResult = CBase;
    CBase = oldBase;
    CnResults = oldCnResults;
    return firstResult;
}

static void call_CERROR (int fb){
    int nParams = ElsVmFallBackSitu[fb].nParams;
    open_stack(nParams);
    *(top-nParams-1) = ElsVmFallBackSitu[fb].function;
    do_call((top-stack)-nParams, ElsVmFallBackSitu[fb].nResults);
}

static void do_call (int base, int nResults){
    int firstResult;
    Object *func = stack+base-1;
    int i;
    if (tag(func) == ELS_TYPE_CERRORPI){
        tag(func) = ELS_TYPE_CMARK;
        firstResult = callC(fvalue(func), base);
    }else if (tag(func) == ELS_TYPE_FC){
        tag(func) = ELS_TYPE_MARK;
        firstResult = els_VmLoadByte(func->value.tf->code, base);
    }else{ 
        open_stack((top-stack)-(base-1));
        stack[base-1] = ElsVmFallBackSitu[_CERROR_FC].function;
        do_call(base, nResults);
        return;
    }
    if (nResults != MULT_RET && top - (stack+firstResult) != nResults) compare_top(firstResult+nResults);
    base--;
    nResults = top - (stack+firstResult); 
    for (i=0; i<nResults; i++) *(stack+base+i) = *(stack+firstResult+i);
    top -= firstResult-base;
}

static void pushsubscript (void){
    if (tag(top-2) != ELS_TYPE_ARRAY) call_CERROR(_CERROR_GETTABLE);
    else {
        Object *h = els_hashget(avalue(top-2), top-1);
        if (h == NULL || tag(h) == ELS_TYPE_NULL) call_CERROR(_CERROR_INDEX);
        else{
            --top;
            *(top-1) = *h;
        }
    }
}

static void storesubscript (void){
    if (tag(top-3) != ELS_TYPE_ARRAY)
    call_CERROR(_CERROR_SETTABLE);
    else{
        Object *h = els_hashinit (avalue(top-3), top-2);
        *h = *(top-1);
        top -= 3;
    }
}

void els_travstack (int (*fn)(Object *)){
    Object *o;
    for(o = top-1; o >= stack; o--)
    fn(o);
}

static void els_message (char *s){
    els_pushstr(s);
    call_CERROR(_CERROR_ERROR);
}

void els_error (char *s){
    els_message(s);
}

static int do_protectedrun (int nResults){
    jmp_buf myErrorJmp;
    int status;
    int oldCBase = CBase;
    jmp_buf *oldErr = errorJmp;
    errorJmp = &myErrorJmp;
    if (setjmp(myErrorJmp) == 0){
        do_call(CBase+1, nResults);
        CnResults = (top-stack) - CBase; 
        CBase += CnResults;  
        status = 0;
    }else{ 
        CBase = oldCBase;
        top = stack+CBase;
        status = 1;
    }
    errorJmp = oldErr;
    return status;
}

static int do_protectedmain (void){
    ELS_FUNC tf;
    int status;
    jmp_buf myErrorJmp;
    jmp_buf *oldErr = errorJmp;
    errorJmp = &myErrorJmp;
    compare_top(1);  
    stack[CBase].tag = ELS_TYPE_FC;
    stack[CBase].value.tf = &tf;
    tf.lineDefined = 0;
    tf.fileName = els_parsedfile;
    tf.code = NULL;
    if (setjmp(myErrorJmp) == 0){
        els_parse(&tf);
        status = do_protectedrun(0);
    }else{
        status = 1;
        compare_top(0);  
    }
    errorJmp = oldErr;
    if (tf.code)els_vm_free(tf.code);
    return status;
}

int els_callfunction (els_Typesystem_Object function){
    if (function == ELS_NO_OBJECT) return 1;
    else{
        open_stack((top-stack)-CBase);
        stack[CBase] = *EvmAddress(function);
        return do_protectedrun (MULT_RET);
    }
}

int els_call (char *funcname){
    Word n = els_vm_findsymbolbyname(funcname);
    open_stack((top-stack)-CBase);
    stack[CBase] = s_object(n);
    return do_protectedrun(MULT_RET);
}


int els_dofile (char *filename){
    int status;
    char *message = els_openfile (filename);
    if (message){
        els_message(message);
        return 1;
    }
    status = do_protectedmain();
    els_closefile();
    return status;
}

int els_dostring(char *string){
  int status;
  char *message = els_openstring(string);
  status = do_protectedmain();
  els_closestring();
  return status;
}

els_Typesystem_Object els_getsubscript (void){
    compare_top(2);
    pushsubscript();
    CBase++; 
    return (Ref(top-1));
}

void els_beginblock (void){
    if (numCblocks < MAX_C_BLOCKS) Cblocks[numCblocks] = CBase;
    numCblocks++;
}
void els_endblock (void){
    --numCblocks;
    if (numCblocks < MAX_C_BLOCKS){
        CBase = Cblocks[numCblocks];
        compare_top(0);
    }
}
els_Typesystem_Object els_createunit (void){
    compare_top(0);
    avalue(top) = els_createarray(0);
    tag(top) = ELS_TYPE_ARRAY;
    incr_top;
    CBase++; 
    return Ref(top-1);
}
els_Typesystem_Object els_getstack (int number){
    if (number <= 0 || number > CnResults) return ELS_NO_OBJECT;
    return CBase-CnResults+number;
}
double els_getnum (els_Typesystem_Object object){
    if (object == ELS_NO_OBJECT) return 0.0;
    if (tonumber (EvmAddress(object))) return 0.0;
    else return (nvalue(EvmAddress(object)));
}
char *els_getstr (els_Typesystem_Object object){
    if (object == ELS_NO_OBJECT) return NULL;
    if (tostring (EvmAddress(object))) return NULL;
    else return (svalue(EvmAddress(object)));
}
void els_pushliteral (char *s){
    tsvalue(top) = els_constant[els_vm_findconstantbyname(s)];
    tag(top) = ELS_TYPE_STRING;
    incr_top;
}
char els_number_tmp[32];
char *els_getvalue(els_Typesystem_Object object){
    
    if(els_isnum(object)){
        els_number_tmp[0]='\0';
        gcvt(els_getnum(object),7,els_number_tmp);
        return els_number_tmp;
    }
    if(els_isstr(object)) return els_getstr(object);
    if(els_isfunc(object)) return "结构->方法";
    if(els_isunit(object)) return "结构->单元";
    return NULL;
}
els_Typesystem_CERRORPI els_getcfunction (els_Typesystem_Object object){
    if (object == ELS_NO_OBJECT || tag(EvmAddress(object)) != ELS_TYPE_CERRORPI) return NULL;
    else return (fvalue(EvmAddress(object)));
}
void *els_getuserdata (els_Typesystem_Object object){
    if (object == ELS_NO_OBJECT || tag(EvmAddress(object)) < ELS_TYPE_USERDATA) return NULL;
    else return (uvalue(EvmAddress(object)));
}


els_Typesystem_Object els_getlocked (int ref){
    compare_top(0);
    *top = *els_vm_getlocked(ref);
    incr_top;
    CBase++;  
    return Ref(top-1);
}




int els_lock (void){
    compare_top(1);
    return els_vm_lock(--top);
}
els_Typesystem_Object els_getglobal (char *name){
    Word n = els_vm_findsymbolbyname(name);
    compare_top(0);
    *top = s_object(n);
    incr_top;
    CBase++;  
    return Ref(top-1);
}

void els_storeglobal (char *name){
    Word n = els_vm_findsymbolbyname(name);
    compare_top(1);
    s_object(n) = *(--top);
}
void els_pushnull (void){
    tag(top) = ELS_TYPE_NULL;
    incr_top;
}
void els_pushnum (float n){
    tag(top) = ELS_TYPE_NUMBER; nvalue(top) = n;
    incr_top;
}
void els_pushstr (char *s){
    tsvalue(top) = els_createstring(s);
    tag(top) = ELS_TYPE_STRING;
    incr_top;
}

void els_pushcfunction (els_Typesystem_CERRORPI fn){
    tag(top) = ELS_TYPE_CERRORPI; fvalue(top) = fn;
    incr_top;
}
void els_pushusertag (void *u, int tag){
    if (tag < ELS_TYPE_USERDATA) return;
    tag(top) = tag; uvalue(top) = u;
    incr_top;
}
void els_pushobject (els_Typesystem_Object o){
    *top = *EvmAddress(o);
    incr_top;
}
void els_vm_pushobject (Object *o){
    *top = *o;
    incr_top;
}
int els_type (els_Typesystem_Object o){
    if (o == ELS_NO_OBJECT) return ELS_TYPE_NULL;
    else return tag(EvmAddress(o));
}
void els_vm_gc_CERROR (Object *o){
    *top = *o;
    incr_top;
    call_CERROR(_CERROR_GC);
}
static void call_arith (char *op){
    els_pushstr(op);
    call_CERROR(_CERROR_ARITH);
}

static void comparison (els_Type_System tag_less, els_Type_System tag_equal, els_Type_System tag_great, char *op){
    Object *l = top-2;
    Object *r = top-1;
    int result;
    if (tag(l) == ELS_TYPE_NUMBER && tag(r) == ELS_TYPE_NUMBER) result = (nvalue(l) < nvalue(r)) ? -1 : (nvalue(l) == nvalue(r)) ? 0 : 1;
    else if (tostring(l) || tostring(r)){
        els_pushstr(op);
        call_CERROR(_CERROR_ORDER);
        return;
    }else result = strcmp(svalue(l), svalue(r));
    top--;
    nvalue(top-1) = 1;
    tag(top-1) = (result < 0) ? tag_less : (result == 0) ? tag_equal : tag_great;
}

static int els_VmLoadByte (Byte *pc, int base){
    while (1){
        OpCode els_vm_bytecode;
        switch (els_vm_bytecode = (OpCode)*pc++){
            case PUSHNULL: tag(top) = ELS_TYPE_NULL; incr_top; break;
            case PUSHNUM0: case PUSHNUM1: case PUSHNUM2:
                tag(top) = ELS_TYPE_NUMBER;
                nvalue(top) = els_vm_bytecode-PUSHNUM0;
                incr_top;
                break;
            case PUSHBYTE: 
                tag(top) = ELS_TYPE_NUMBER; nvalue(top) = *pc++; incr_top; break;
            case PUSHWORD:{
                CodeWord code;
                get_word(code,pc);
                tag(top) = ELS_TYPE_NUMBER; nvalue(top) = code.w;
                incr_top;
            }
                break;
            case PUSHFLOAT:{    
                CodeFloat code;
                get_float(code,pc);
                tag(top) = ELS_TYPE_NUMBER; nvalue(top) = code.f;
                incr_top;
            }
                break;
            case PUSHSTRING:{
                CodeWord code;
                get_word(code,pc);
                tag(top) = ELS_TYPE_STRING; tsvalue(top) = els_constant[code.w];
                incr_top;
            }
                break;
            case PUSHFC:{
                CodeCode code;
                get_code(code,pc);
                els_vm_insertfunction(code.tf);
                top->tag = ELS_TYPE_FC;
                top->value.tf = code.tf;
                incr_top;
            }
                break;
            case PUSHLET0: case PUSHLET1: case PUSHLET2:
            case PUSHLET3: case PUSHLET4: case PUSHLET5:
            case PUSHLET6: case PUSHLET7: case PUSHLET8:
            case PUSHLET9: 
                *top = *((stack+base) + (int)(els_vm_bytecode-PUSHLET0)); incr_top; break;
            case PUSHLET: *top = *((stack+base) + (*pc++)); incr_top; break;
            case PUSHGLOBAL:{
                CodeWord code;
                get_word(code,pc);
                *top = s_object(code.w);
                incr_top;
            }
                break;
            case PUSHINDEXED:
                pushsubscript();
                break;
            case PUSHSELF:{
                Object receiver = *(top-1);
                CodeWord code;
                get_word(code,pc);
                tag(top) = ELS_TYPE_STRING; tsvalue(top) = els_constant[code.w];
                incr_top;
                pushsubscript();
                *top = receiver;
                incr_top;
                break;
            }
            case STORELET0: case STORELET1: case STORELET2:
            case STORELET3: case STORELET4: case STORELET5:
            case STORELET6: case STORELET7: case STORELET8:
            case STORELET9:
                *((stack+base) + (int)(els_vm_bytecode-STORELET0)) = *(--top);
                break;
            case STORELET: *((stack+base) + (*pc++)) = *(--top); break;
            case STOREGLOBAL:{
                CodeWord code;
                get_word(code,pc);
                s_object(code.w) = *(--top);
            }
                break;
            case STOREINDEXED0:
                storesubscript();
                break;
            case STOREINDEXED:{
                int n = *pc++;
                if (tag(top-3-n) != ELS_TYPE_ARRAY){
                    els_checkstack(top+2);
                    *(top+1) = *(top-1);
                    *(top) = *(top-2-n);
                    *(top-1) = *(top-3-n);
                    top += 2;
                    call_CERROR(_CERROR_SETTABLE);
                }else{
                    Object *h = els_hashinit (avalue(top-3-n), top-2-n);
                    *h = *(top-1);
                    top--;
                }
            }
                break;
            case STORELIST0:
            case STORELIST:{
                int m, n;
                Object *arr;
                if (els_vm_bytecode == STORELIST0) m = 0;
                else m = *(pc++) * FIELDS_PER_FLUSH;
                n = *(pc++);
                arr = top-n-1;
                while (n){
                    tag(top) = ELS_TYPE_NUMBER; nvalue(top) = n+m;
                    *(els_hashinit (avalue(arr), top)) = *(top-1);
                    top--;
                    n--;
                }
            }
                break;
            case STORERECORD:{
                int n = *(pc++);
                Object *arr = top-n-1;
                while (n){
                    CodeWord code;
                    get_word(code,pc);
                    tag(top) = ELS_TYPE_STRING; tsvalue(top) = els_constant[code.w];
                    *(els_hashinit (avalue(arr), top)) = *(top-1);
                    top--;
                    n--;
                }
            }
                break;
            case ADJUST0:
                compare_top(base);
                break;
            case ADJUST:
                compare_top(base + *(pc++));
                break;
            case CREATEARRAY:{
                CodeWord size;
                get_word(size,pc);
                avalue(top) = els_createarray(size.w);
                tag(top) = ELS_TYPE_ARRAY;
                incr_top;
            }
                break;
            case EQOP:{
                int res = els_equalObj(top-2, top-1);
                --top;
                tag(top-1) = res ? ELS_TYPE_NUMBER : ELS_TYPE_NULL;
                nvalue(top-1) = 1;
            }
                break;
            case LTOP:
                comparison(ELS_TYPE_NUMBER, ELS_TYPE_NULL, ELS_TYPE_NULL, "lt");
                break;
            case LEOP:
                comparison(ELS_TYPE_NUMBER, ELS_TYPE_NUMBER, ELS_TYPE_NULL, "le");
                break;
            case GTOP:
                comparison(ELS_TYPE_NULL, ELS_TYPE_NULL, ELS_TYPE_NUMBER, "gt");
                break;
            case GEOP:
               comparison(ELS_TYPE_NULL, ELS_TYPE_NUMBER, ELS_TYPE_NUMBER, "ge");
                break;
            case ADDOP:{
                Object *l = top-2;
                Object *r = top-1;
                if (tonumber(r) || tonumber(l)) call_arith("add");
                else{
                    nvalue(l) += nvalue(r);
                    --top;
                }
            }
                break;
            case SUBOP:{
                Object *l = top-2;
                Object *r = top-1;
                if (tonumber(r) || tonumber(l)) call_arith("sub");
                else{
                    nvalue(l) -= nvalue(r);
                    --top;
                }
            }
                break;
            case MULTOP:{
                Object *l = top-2;
                Object *r = top-1;
                if (tonumber(r) || tonumber(l)) call_arith("mul");
                else{
                    nvalue(l) *= nvalue(r);
                    --top;
                }
            }
                break;
            case DIVOP:{
                Object *l = top-2;
                Object *r = top-1;
                if (tonumber(r) || tonumber(l)) call_arith("div");
                else{
                    nvalue(l) /= nvalue(r);
                    --top;
                }
            }
                break;
            case POWOP:
                call_arith("pow");
                break;
            case CONCOP:{
                Object *l = top-2;
                Object *r = top-1;
                if (tostring(r) || tostring(l)) call_CERROR(_CERROR_CONCAT);
                else{
                    tsvalue(l) = els_createstring (els_strconc(svalue(l),svalue(r)));
                    --top;
                }
            }
                break;
            case MINUSOP:
                if (tonumber(top-1)){
                    tag(top) = ELS_TYPE_NULL;
                    incr_top;
                    call_arith("unm");
                }
                else nvalue(top-1) = - nvalue(top-1);
                break;
            case NOTOP:
                tag(top-1) = (tag(top-1) == ELS_TYPE_NULL) ? ELS_TYPE_NUMBER : ELS_TYPE_NULL;
                nvalue(top-1) = 1;
                break;
            case ONTJMP:{
                CodeWord code;
                get_word(code,pc);
                if (tag(top-1) != ELS_TYPE_NULL) pc += code.w;
            }
                break;
            case ONFJMP:{
                CodeWord code;
                get_word(code,pc);
                if (tag(top-1) == ELS_TYPE_NULL) pc += code.w;
            }
                break;
            case JMP:{
                CodeWord code;
                get_word(code,pc);
                pc += code.w;
            }
                break;
           case UPJMP:{
                CodeWord code;
                get_word(code,pc);
                pc -= code.w;
            }
                break;
            case IFFJMP:{
                CodeWord code;
                get_word(code,pc);
                top--;
                if (tag(top) == ELS_TYPE_NULL) pc += code.w;
            }
                break;
            case IFFUPJMP:{
                CodeWord code;
                get_word(code,pc);
                top--;
                if (tag(top) == ELS_TYPE_NULL) pc -= code.w;
            }
                break;
            case POP:
                --top;
                break;
            case CALLFUNC:{
                int nParams = *(pc++);
                int nResults = *(pc++);
                int newBase = (top-stack)-nParams;
                do_call(newBase, nResults);
            }
                break;
            case RETCODE0:
                return base;
            case RETCODE:
                return base+*pc;
            case SETLINE:{
                CodeWord code;
                get_word(code,pc);
                if ((stack+base-1)->tag != ELS_TYPE_LINE){
                    open_stack((top-stack)-base);
                    base++;
                    (stack+base-1)->tag = ELS_TYPE_LINE;
                }
                (stack+base-1)->value.i = code.w;
                break;
            }
            default:
                els_error ("锟斤拷锟截达拷锟斤拷 - 锟街斤拷锟斤拷锟斤拷锟�.Els-vm锟叫凤拷锟斤拷锟斤拷锟届常!");
        }
    }
}


